#define PrynEditorInternal
#define PrynEditorImplementation
#include <prynEditor.h>

//#define PrynEditorCycleBackground // Causes the background paint color to cycle to show dirty regions.

#define colorOutline prynColorRGB (128, 128, 128)
#define colorOutlineHover prynColorRGB (255, 255, 255)
#define colorOutlineTarget prynColorRGB (0, 0, 0)
#define colorComponentBackground prynColorRGB (230, 230, 230)
#define colorLine prynColorRGB (0, 0, 0)
#define componentMargin 10
#define prynEditorBoardBackgroundColor prynColorRGB (186, 196, 206)

typedef struct
{
	int x, y; // Location on the board where we're picking.
	PrynPin *result; // Resulting value or 0.
} PickPinInfo;

static bool EachPickPin (PrynEditorComponent *component, PrynPin *pin, int x, int y, void *data)
{
	PickPinInfo *info = (PickPinInfo *) data;
	int sx = x - 1, sy = y - 1, ex = x + prynEditorPinSize + 1, ey = y + prynEditorPinSize + 1, px = info->x, py = info->y;
	
	if (px >= sx && py >= sy && px < ex && py < ey)
	{
		info->result = pin;
		return false;
	}
	
	return true;
}

static bool EachPaintPin (PrynEditorComponent *component, PrynPin *pin, int x, int y, void *data)
{
	PrynGuiPainter *painter = (PrynGuiPainter *) data;
	PrynEditorBoard *board = component->board;
	PrynColor outer, inner;
	PrynValue *pinValue;

	prynPinValue (pin, &pinValue);
	prynValueColors (pinValue, &outer, &inner);
	prynGuiPainterSetPenSolid (painter, 2, pin == board->targetPin ? colorOutlineTarget : pin == board->overPin ? colorOutlineHover : outer);
	prynGuiPainterSetBrushColor (painter, inner);
	prynGuiPainterRectangle (painter, x, y, x + prynEditorPinSize, y + prynEditorPinSize); 
	return true;
}

typedef struct { PrynPin *pin; int *x, *y; } PinPositionInfo; 

static bool prynEditorComponentEachPinPosition (PrynEditorComponent *component, PrynPin *pin, int x, int y, void *data)
{
	PinPositionInfo *info = (PinPositionInfo *) data;
	if (pin == info->pin) { *info->x = x, *info->y = y; return false; }
	return true;
}

// Retrieve the position of the top-left corner of the pin.
static void PinPosition (PrynEditorBoard *board, PrynPin *pin, int *x, int *y)
{
	PrynEditorComponent *component;
	PinPositionInfo info;
	
	info.pin = pin, info.x = x, info.y = y;
	prynDListEach (component, board->components)
		if (!prynEditorComponentEachPin (component, &prynEditorComponentEachPinPosition, &info))
			return;
}

PrynEditorExport (PrynResult, prynEditorBoardPickComponent, (PrynEditorBoard *board, int x, int y, PrynEditorComponent **output))
{
	PrynEditorComponent *component;

	if (output) *output = 0;
	if (!board) return PrynResultNullArgument;
	
	prynDListEachReverse (component, board->components)
	{
		if (x >= component->x && y >= component->y && x < component->x + component->w && y < component->y + component->h)
		{
			if (output)
				*output = component;
			return PrynResultSuccess;
		}
	}
	
	return PrynResultDone;
}

PrynEditorExport (PrynResult, prynEditorBoardPickComponentOrPin, (PrynEditorBoard *board, int x, int y, PrynEditorComponent **outputComponent, PrynPin **outputPin))
{
	PrynEditorComponent *component;

	if (outputComponent) *outputComponent = 0;
	if (outputPin) *outputPin = 0;
	if (!board || !outputComponent || !outputPin) return PrynResultNullArgument;
	
	prynDListEachReverse (component, board->components)
	{
		PickPinInfo info;
		
		info.x = x, info.y = y, info.result = 0;
		
		if (!prynEditorComponentEachPin (component, EachPickPin, &info)
			|| (x >= component->x && y >= component->y && x < component->x + component->w && y < component->y + component->h))
		{
			*outputComponent = component;
			*outputPin = info.result;
			return PrynResultSuccess;
		}
	}
	
	return PrynResultDone;
}

PrynEditorExport (PrynResult, prynAddEditorComponents, (PrynEditorBoard *board, PrynComponent **components, size_t componentCount))
{
	PrynResult result = PrynResultSuccess;
	size_t index;
	
	prynAssert (board, NullArgument);
	if (!componentCount) goto success;
	prynAssert (components, NullArgument);
	
	for (index = 0; index < componentCount; index ++)
	{
		PrynComponent *component = components [index];
		PrynEditorComponent *editor;
		
		prynAssert (components [index], NullArgument);
		prynEditorComponentFrom (&editor, board, component);
	}
	
success:
	if (0) { failure:;
	}
	
	return result;
}

bool EachPaintPinConnection (PrynEditorComponent *component, PrynPin *pin, int sx, int sy, void *data)
{
	#define imageWidth 2
	#define imageHeight 16
	const int penWidth = 2;
	PrynColor image [imageWidth * imageHeight];

	PrynGuiPainter *painter = (PrynGuiPainter *) data;
	PrynEditorPin *editor;
	PrynEditorBoard *board = component->board;
	PrynPin *pinConnected;
	PrynValue *pinValue;
	int ex, ey;
	PrynColor fallback;

	prynEditorPinFrom (&editor, component, pin);
	
	if (!PrynIsTrue (prynPinIsConnected (pin)) || !editor)
		return true;
	
	prynGuiPainterSetPenSolid (painter, 2, colorLine);
	
	sx += prynEditorPinSizeHalf, sy += prynEditorPinSizeHalf;
	prynPinConnected (pin, &pinConnected);
	PinPosition (component->board, pinConnected, &ex, &ey);
	ex += prynEditorPinSizeHalf, ey += prynEditorPinSizeHalf;
	
	prynPinValue (pin, &pinValue);
	prynValueConnectionImage (pinValue, imageWidth, imageHeight, image, &fallback);
	prynGuiPainterSetPenSolid (painter, penWidth + 2, prynEditorBoardBackgroundColor);
	prynEditorPathPaintRoute (&editor->route, painter);
	prynGuiPainterSetPenPattern (painter, penWidth, fallback, imageWidth, imageHeight, image, -board->ticks, PrynGuiPenRepeatSolid);
	prynEditorPathPaintRoute (&editor->route, painter);
	
	#undef imageWidth
	#undef imageHeight
	
	return true;
}

bool EachUpdatePinRoute (PrynEditorComponent *component, PrynPin *pin, int sx, int sy, void *data)
{
	PrynEditorPin *editor;

	if (!PrynIsTrue (prynPinIsConnected (pin)))
		return true;
	prynEditorPinFrom (&editor, component, pin);
	prynEditorPinUpdateRoute (editor);
	return true;
}

PrynEditorExport (PrynResult, prynEditorBoardUpdateConnections, (PrynEditorBoard *board))
{
	PrynEditorComponent *editor;
	PrynResult result = 0;

	if (!board) return PrynResultNullArgument;
	if (!board->pathDirty) return PrynResultDone;
	
	board->pathDirty = false;

	prynDListEach (editor, board->components)
		prynEditorComponentUpdatePins (editor);

	if ((result = prynEditorPathSetup (board, &board->path)) < 0)
		return result;
	
	prynDListEach (editor, board->components)
		prynEditorComponentEachOutputPin (editor, &EachUpdatePinRoute, 0);
	
	return PrynResultSuccess;
}

#ifdef PrynEditorCycleBackground
static int cycle = 0;
#endif

PrynEditorExport (PrynResult, prynEditorBoardPaint, (PrynEditorBoard *board, PrynGuiPainter *painter))
{
	PrynEditorComponent *editor;
	PrynResult result = 0;
	
	if (!board || !painter) return PrynResultNullArgument;
	
#ifndef PrynEditorCycleBackground
	prynGuiPainterClear (painter, prynEditorBoardBackgroundColor);
	prynGuiPainterTranslate (painter, -board->scrollX, -board->scrollY);
#else
	cycle = (cycle + 16) % 256;
	prynGuiPainterClear (painter, prynColorRGB (cycle, cycle, cycle));
#endif
	
	// Paint the components.
	prynDListEach (editor, board->components)
	{
		PrynComponent *component = editor->component;
		PrynString displayName;
		int w = editor->w;
		
		int pw = 4; // Padding between the border and the contents.
		int bw = 2; // Width of the border.
		int x, y, h, ex, ey, cx, cy, bx, by, bex, bey;
		
		prynComponentDisplayName (component, &displayName);
		if (!displayName.length)
			prynComponentName (component, &displayName);
		
		prynGuiPainterFontHeight (painter, 10);
		if (!editor->h)
		{
			int lines, advance;
			prynGuiPainterCountTextLines (painter, &displayName, w, &lines);
			prynGuiPainterTextAdvance (painter, &advance);
			editor->h = pw * 2 + bw + lines * advance;
		}
		
		x = editor->x, y = editor->y;
		h = editor->h;
		ex = x + w, ey = y + h, cx = x + w / 2, cy = y + h / 2;
		bx = x + bw / 2 + pw, by = y + bw / 2 + pw;
		bex = ex - bw / 2 - 1 - pw, bey = ey - bw / 2 - 1 - pw;
		
		prynGuiPainterSetPenSolid (painter, bw, board->over == editor && !board->overPin ? colorOutlineHover : colorOutline);
		prynGuiPainterSetBrushColor (painter, colorComponentBackground);
		prynGuiPainterRectangle (painter, x, y, ex, ey);
		
		prynGuiPainterSetTextAlign (painter, PrynGuiTextAlignCenter | PrynGuiTextAlignTop);
		prynGuiPainterPrintArea (painter, bx, by, bex, bey, &displayName);
		
		prynEditorComponentEachPin (editor, &EachPaintPin, painter);
	}
	
	// Update pin locations. This is after rendering in case the component changed size.
	prynDListEach (editor, board->components)
		prynEditorComponentUpdatePins (editor);
		
	// Update routing as necessary.
	prynEditorBoardUpdateConnections (board);
	
	//prynEditorPathPaintLattice (&board->path, painter);
	
	// Paint the pin connections.
	prynDListEach (editor, board->components)
	{
		PrynComponent *component = editor->component;
		prynEditorComponentEachOutputPin (editor, &EachPaintPinConnection, painter);
	}
	
	// Paint anything related to the current action.
	switch (board->action)
	{
		default:
			break;
			
		case PrynEditorBoardActionDraggingPin:
			prynGuiPainterSetPenSolid (painter, 2, colorLine);
			prynGuiPainterLine (painter, 2, board->dragStartX, board->dragStartY, board->mouseX, board->mouseY);
			break;
	}
	
	return PrynResultSuccess;
}

PrynEditorExport (PrynResult, prynEditorBoardShuffle, (PrynEditorBoard *board, int iterations, int percentEach))
{
	PrynEditorComponent *over;
	int iteration;

	if (!board) return PrynResultNullArgument;
	over = board->action ? board->over : 0;
	
	for (iteration = 0; iteration < iterations; iteration ++)
	{
		bool overlapped = false; // Set in the loop if anything is moved; used to terminate the iterations if it's pointless.
		PrynEditorComponent *component;

		prynDListEach (component, board->components)
		{
			PrynEditorComponent *overlap = component;
			int left = component->x - componentMargin * 2, top = component->y - componentMargin * 2, width = component->w + componentMargin * 4, height = component->h + componentMargin * 4, right = left + width, bottom = top + height, centerX = left + width / 2, centerY = top + height / 2;
			PrynRectangle area;
			
			if (component == over)
				continue;
			prynRectangle (&area, left, top, right, bottom);
			while (!prynEditorBoardIterateOverlappingComponents (board, &overlap, &area))
			{
				int overlapCenterX, overlapCenterY;
				int pushW = 0, pushH = 0;

				if (overlap == component || overlap == over)
					continue;
				overlapCenterX = overlap->x + overlap->w / 2, overlapCenterY = overlap->y + overlap->h / 2;
				
				if (overlap->y < bottom && centerY < overlapCenterY)
					pushH = (bottom - overlap->y) * percentEach / 200 + 1;
				else if (overlap->y + overlap->h > top && centerY >= overlapCenterY)
					pushH = (top - (overlap->y + overlap->h)) * percentEach / 200 - 1;
				
				if (overlap->x < right && centerX < overlapCenterX)
					pushW = (right - overlap->x) * percentEach / 200 + 1;
				else if (overlap->x + overlap->w > left && centerX >= overlapCenterX)
					pushW = (left - (overlap->x + overlap->w)) * percentEach / 200 - 1;
				
				// Don't push what we've locked up.
				if (overlap == over)
					pushW *= 2, pushH *= 2;
				
				// Only choose to push in one direction, the one with the least distance to go.
				if (prynAbsolute (pushW) > prynAbsolute (pushH)) pushW = 0;
				else pushH = 0;

				// If anything is moved, set overlapped.
				if (pushW || pushH) overlapped = true;
				
				prynEditorComponentMove (component, component->x - pushW, component->y - pushH);
				if (overlap != over)
					prynEditorComponentMove (overlap, overlap->x + pushW, overlap->y + pushH);
			}
		}

		// Stop if nothing was moved.
		if (!overlapped) break;
	}
	
	return PrynResultSuccess;
}

PrynEditorExport (PrynResult, prynEditorBoardDirtyConnections, (PrynEditorBoard *board))
{
	PrynEditorComponent *component;

	if (!board) return PrynResultNullArgument;
	
	prynDListEach (component, board->components)
		prynEditorComponentDirtyOutputConnections (component);
	return PrynResultSuccess;
}

